Skip to content

Conversation

@Jondolf
Copy link
Member

@Jondolf Jondolf commented Jan 24, 2026

Objective

Implement a new broad phase algorithm using Bounding Volume Hierarchies (BVH) provided by OBVHS. This replaces our existing broad phase that uses Sweep and Prune (SAP).

The goal is to greatly reduce overhead for large scenes with lots of colliders. Updating and querying the acceleration structures should be efficient, and static or sleeping bodies should have minimal runtime cost. The existing SAP broad phase is very inefficient at this, and scales poorly even with only static geometry.

Additionally, we can reuse these BVHs for accelerating spatial queries. Currently, they just use Parry's BVH, which we update and manage very inefficiently. I will leave migrating spatial queries to the new BVHs for a follow-up however, to keep the diff more manageable.

Huge thanks to @DGriffin91 for implementing incremental leaf insertion and removal, partial rebuilds, and more for OBVHS to better suit our needs <3

Solution

  • ColliderTree type that contains a Bvh2, its collision proxies, and a workspace for reusing allocations
  • ColliderTrees resource, containing a separate ColliderTree for dynamic, kinematic, static, and standalone (=no body) colliders
  • ColliderTreePlugin that manages ColliderTrees for a collider type C
    • Adds/removes colliders to/from trees, and updates proxy data
    • Updates ColliderAabbs and EnlargedAabbs of colliders
    • Manages "moved proxies": colliders that moved past their EnlargedAabb, or otherwise require updating the tree
    • Optimizes trees to maintain good query performance, in an async task that runs concurrently with the narrow phase and solver
      • Dynamic or kinematic: Full rebuilds, partial rebuilds, or reinsertion based on ColliderTreeOptimization settings (default is an adaptive hybrid that switches between them)
      • Static or standalone: Reinsertion
  • BroadPhaseCorePlugin that sets up the resources, system sets, and diagnostics required for the broad phase
  • BvhBroadPhasePlugin that implements a BVH-based broad phase algorithm
    • Traverses each tree for every moved proxy (colliders that don't move beyond their enlarged AABB don't need to check for new overlaps)
    • Creates edges in the contact graph for every new overlap between enlarged AABBs

Testing

Ran examples and tested adding/removing/enabling/disabling colliders and changing various different configurations.

Note for Reviewers

The commit history here is a bit borked for some reason, and contains some older commits that are unrelated. The first real commit is "Implement dynamic BVH broad phase with OBVHS (WIP)" (a603c5e).


Showcase

Below is a test scene with 10k dynamic bodies that cannot sleep, with approximately 25% of them being moved each frame. Spatial queries are disabled.

Before, with the SAP broad phase:

Before

After, with the BVH broad phase:

After

Future Work

  • Make spatial queries use the new BVH (adopt Draft: Make spatial queries generic over colliders #810)
  • Explore making collider trees more flexible, ex: allow users to easily create their own trees and perform queries on them
  • Try doing a small amount of reinsertion on the static tree at each step to improve its query performance
  • Optimize partial rebuilds further

Todo

  • Test more
  • Show perf difference with a fully static but much larger scene
  • Bug Griffin to publish a new OBVHS release with the insertion-removal stuff

- The broad phase now emits new collision pairs and stores all pairs in a HashSet

- Contact status and kind is now tracked with `ContactPairFlags` instead of booleans

- The narrow phase adds new collision pairs, updates existing pairs, and responds to state changes separately instead of overwriting and doing extra work for persistent contact

- State changes are tracked with bit vectors (bit sets), which are fast to iterate serially

- The narrow phase is responsible for collision events instead of the `ContactReportingPlugin`
- Renamed `BroadCollisionPairs` to `BroadPhasePairSet`
- Added `BroadPhasePairSet` for fast pair lookup with new `PairKey`
- Improve broad phase docs
…pt-in

- Removed `BroadPhaseAddedPairs`
- Renamed `BroadPhasePairSet` to `BroadPhasePairs`
- Moved contact creation to broad phase to improve persistence
- Removed some graph querying overhead from contact pair removal by using the `EdgeIndex` directly
- Made collision events opt-in with `CollisionEventsEnabled` component
- Improved a lot of docs
@Jondolf Jondolf added C-Performance Improvements or questions related to performance A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality M-Migration-Guide A breaking change to Avian's public API that needs to be noted in a migration guide D-Complex Challenging from a design or technical perspective. Ask for help if you'd like to tackle this! S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jan 24, 2026
@Jondolf Jondolf marked this pull request as draft January 24, 2026 00:34
@Jondolf Jondolf marked this pull request as ready for review January 26, 2026 16:05
@eswartz
Copy link

eswartz commented Jan 27, 2026

I've got some issues with this branch (though not on main):

-- I am using collision hooks, but get a panic at startup regarding conflicting schedule conflicts inside avian.

Unfortunately I lost the info (since I was working inside less...) but this is the structure of what I was doing, just in case the SystemParam structure is relevant:

.add_plugins(PhysicsPlugins::default()
            .with_collision_hooks::<GeometryCollisionHooks>()
            ,
        )

where:

#[derive(SystemParam)]
pub struct GeometryCollisionHooks<'w, 's> {
    player_q: Query<'w, 's, &'static PlayerMovement, With<Player>>,
    projectile_q: Query<'w, 's, (), With<Projectile>>,
    parent_q: Query<'w, 's, &'static ChildOf>,
    area_q: Query<'w, 's, &'static FuncArea>,
}

-- after turning that off for now, when my RigidBody::Kinematic + SweptCcd character hits a Sensor , I get this panic:

   0: bevy_ecs::system::function_system::system
           with name="avian3d::collision::narrow_phase::update_narrow_phase<avian3d::collision::collider::parry::Collider, ()>"
             at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/system/function_system.rs:52

thread 'main' (356775) panicked at /home/ejs/.cargo/git/checkouts/avian-5a22c167119f3550/39a9d32/crates/avian3d/../../src/dynamics/solver/islands/mod.rs:518:9:
assertion failed: contact.island.is_none()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `avian3d::collision::narrow_phase::update_narrow_phase<avian3d::collision::collider::parry::Collider, ()>`!
Encountered a panic in system `avian3d::schedule::run_physics_schedule`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `bevy_time::fixed::run_fixed_main_schedule`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!

-- unfortunately, after this stage, I can't rebuild anymore without glam-related errors :slight_frown:

It looks like obvhs depends on [email protected] while avian depends on [email protected] and somehow it breaks things:

   Compiling avian3d v0.6.0-dev (https://github.com/Jondolf/avian.git?branch=obvhs#39a9d32d)
error[E0277]: the trait bound `glam::f32::sse2::vec3a::Vec3A: From<bevy_math::Vec3>` is not satisfied
   --> /home/ejs/.cargo/git/checkouts/avian-5a22c167119f3550/39a9d32/crates/avian3d/../../src/collision/collider/mod.rs:557:34
    |
557 |             min: value.min.f32().into(),
    |                                  ^^^^ the trait `From<bevy_math::Vec3>` is not implemented for `glam::f32::sse2::vec3a::Vec3A`
    |
    = help: the following other types implement trait `From<T>`:
              `glam::f32::sse2::vec3a::Vec3A` implements `From<(f32, f32, f32)>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<(glam::f32::vec2::Vec2, f32)>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<[f32; 3]>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<__m128>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<glam::bool::bvec3::BVec3>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<glam::bool::sse2::bvec3a::BVec3A>`
              `glam::f32::sse2::vec3a::Vec3A` implements `From<glam::f32::vec3::Vec3>`
    = note: there are multiple different versions of crate `glam` in the dependency graph
    = help: you can use `cargo tree` to explore your dependency tree
    = note: required for `bevy_math::Vec3` to implement `Into<glam::f32::sse2::vec3a::Vec3A>`

(and more)

(I see that last error appears in the Ubuntu build, so I guess you have a repro there)

@eswartz
Copy link

eswartz commented Jan 27, 2026

After doing cargo update (maybe that was my problem), I was able to build again and can supply more information on the with_collision_hooks panic:

   0: bevy_ecs::schedule::schedule::schedule
           with name=PhysicsSchedule
             at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/schedule/schedule.rs:532
   1: bevy_ecs::system::function_system::system
           with name="avian3d::schedule::run_physics_schedule"
             at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/system/function_system.rs:52

thread 'main' (1052376) panicked at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/schedule/schedule.rs:536:13:
Error when initializing schedule PhysicsSchedule: 1 pairs of systems with conflicting data access have indeterminate execution order. Consider adding `before`, `after`, or `ambiguous_with` relationships between these:
 -- collect_collision_pairs<()> (in sets BroadPhase, CollectCollisions) and collect_collision_pairs<GeometryCollisionHooks<'_, '_>> (in sets BroadPhase, CollectCollisions)
    conflict on: ["avian3d::collider_tree::ColliderTrees", "avian3d::collision::contact_types::contact_graph::ContactGraph", "avian3d::collision::diagnostics::CollisionDiagnostics"]

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `avian3d::schedule::run_physics_schedule`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `bevy_time::fixed::run_fixed_main_schedule`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!

with the top of the backtrace:

   3: unwrap_or_else<(), bevy_ecs::schedule::error::ScheduleBuildError, bevy_ecs::schedule::schedule::{impl#3}::run::{closure_env#0}>
             at /home/ejs/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1622:23
   4: run
             at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/schedule/schedule.rs:535:32
   5: {closure#0}
             at /home/ejs/.cargo/git/checkouts/avian-5a22c167119f3550/39a9d32/crates/avian3d/../../src/schedule/mod.rs:268:22
   6: try_schedule_scope<(), avian3d::schedule::PhysicsSchedule, avian3d::schedule::run_physics_schedule::{closure_env#0}>
             at /home/ejs/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0/src/world/mod.rs:3665:21
   7: run_physics_schedule
             at /home/ejs/.cargo/git/checkouts/avian-5a22c167119f3550/39a9d32/crates/avian3d/../../src/schedule/mod.rs:241:19
   8: call_mut<fn(&mut bevy_ecs::world::World, bevy_ecs::system::system_param::Local<avian3d::schedule::IsFirstRun>), (&mut bevy_ecs::world::World, bevy_ecs::system::system_param::Local<avian3d::schedule::IsFirstRun>)>
             at /home/ejs/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:166:5
   9: call_mut<(&mut bevy_ecs::world::World, bevy_ecs::system::system_param::Local<avian3d::schedule::IsFirstRun>), fn(&mut bevy_ecs::world::World, bevy_ecs::system::system_param::Local<avian3d::schedule::IsFirstRun>)>
             at /home/ejs/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:298:21
...

@Jondolf
Copy link
Member Author

Jondolf commented Jan 27, 2026

Hi, thanks for trying it out! That collision hook crash should hopefully be fixed now if you run cargo update.

when my RigidBody::Kinematic + SweptCcd character hits a Sensor , I get this panic

Hmm the crash looks like it'd be unrelated to this PR, since it's about simulation islands and not the BVH stuff, but I'll see if I can reproduce this when I have time.

Edit: Or hmm, I just fixed one bug where the broad phase wasn't setting some flags correctly for contact data, which could have been related? Not sure

@eswartz
Copy link

eswartz commented Jan 27, 2026

Hi, thanks for trying it out! That collision hook crash should hopefully be fixed now if you run cargo update.

Yes, it's fixed!

Edit: Or hmm, I just fixed one bug where the broad phase wasn't setting some flags correctly for contact data, which could have been related? Not sure

Yes, that panic is fixed too.


Now that things are working again, there's one issue with an unexpected behavior change:

In my aforementioned GeometryCollisionHooks, I am now seeing cases where ContactPair.body2 is None where it wasn't before. The picture shows the components on the "second" object (the Collider is a convex collider) for a plain cuboid).

image

On main, the order of colliders/bodies is switched -- though I deal with that already -- but the body1/body2 entities are the same as collider1/collider2, given that I have the mesh and colliders on the same entity throughout. Not sure if that's an intentional change.


As far as performance goes, I was testing this because I have a silly case where the player can walk over 10240 such tiles. That used to go at ~10-20 FPS in debug builds (well, optlevel=1), but now runs at 60fps! Good work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality C-Performance Improvements or questions related to performance D-Complex Challenging from a design or technical perspective. Ask for help if you'd like to tackle this! M-Migration-Guide A breaking change to Avian's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants